/*
 * ==================================
 * =========== PROJECT 5 ============
 * ======== Packet Analyzer =========
 * ==================================
 *
 * Name:            Luke Valenta
 * Directory ID:    lvalenta
 * Student ID:      112113230
 */

#include <pcap.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include "analyze.h"

#if !defined(PCAP_NETMASK_UNKNOWN)
/*
 *  Value to pass to pcap_compile() as the netmask if you don't know what
 *  the netmask is.
 * 
 */
#define PCAP_NETMASK_UNKNOWN    0xffffffff
#endif

typedef enum {
    false,
    true
} bool;

void print_packet(const struct pcap_pkthdr * pkt_header, const u_char * pkt_data);
void packet_handler(u_char *user_args, const struct pcap_pkthdr *pkt_header, const u_char *pkt_data);
int timeval_subtract (struct timeval * result, struct timeval * x, struct timeval * y);

/* Global variable keeps track of the link header length */
int g_linkhdrlen = 0;

int main(int argc, char ** argv) {

    pcap_t *handle;                     /* Session handle */
    char *fname;                        /* File to read from */
    struct bpf_program fp;              /* The compiled filter */
    char errbuf[PCAP_ERRBUF_SIZE];      /* Error string */
    char *filter_exp;                   /* Filter for trace file */

    if(argc > 2) {
        filter_exp = malloc(strlen(argv[2]) + 1);
        strncpy(filter_exp, argv[2], strlen(argv[2]) + 1);
    } else {
        char *str = ""; /* default filter */
        filter_exp = malloc(strlen(str) + 1);
        strncpy(filter_exp, str, strlen(str) + 1);
    }

    fname =  argv[1];
    if(!(handle = pcap_open_offline(fname, errbuf))) {
        fprintf(stderr, "Couldn't open file %s: %s\n", fname, errbuf);
        return(2);
    }

    /* Compile and apply filters */
    if (pcap_compile(handle, &fp, filter_exp, 0, PCAP_NETMASK_UNKNOWN) == -1) {
        fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(handle));
        return(2);
    }
    if (pcap_setfilter(handle, &fp) == -1) {
        fprintf(stderr, "Couldn't install filter %s: %s\n", filter_exp, pcap_geterr(handle));
        return(2);
    }

    switch (pcap_datalink(handle)) {
        case DLT_EN10MB:
            g_linkhdrlen = 14;
            break;
        case DLT_PPP:
            g_linkhdrlen = 4;
            break;
        default:
            fprintf(stderr, "Unsupported datalink (%d)\n", pcap_datalink(handle));
            return -1;
    }


    /* Print the key for a data file with TCP transmission */
    printf("rel_ts\tip_id\tsend_ip\trecv_ip\tsend_port\trecv_port\tseq_num\tack_num\twin_sz\ttotal_size\tdata_size\n");

    /* Loop until the entire 'savefile' has been read */
    if((pcap_loop(handle, -1, packet_handler, NULL)) != 0) {
        /* An error has occurred */
        pcap_perror(handle, "analyze: pcap_loop");
    }

    free(filter_exp);
    pcap_close(handle);
    return 0;
}


/* Callback function called by pcap_loop */
void packet_handler(u_char *user_args, const struct pcap_pkthdr *pkt_header, const u_char *pkt_data) {
    print_packet(pkt_header, pkt_data);
}

void print_packet(const struct pcap_pkthdr * pkt_header, const u_char * pkt_data) { 
    //const struct sniff_ethernet *ethernet; /* The ethernet header */
    const struct sniff_ip *ip; /* The IP header */
    const struct sniff_tcp *tcp; /* The TCP header */
    const u_char *payload; /* Packet payload */
    u_int size_ip;
    u_int size_tcp;
    char src_ip[16], dst_ip[16];

    /* Used to get the relative sequence and acknowledgment numbers */
    //static int first_sn, first_ack;
    //static int last_sn, last_ack;

    /* Used to compute the relative timestamp on a packet */
    static bool start_found = false;
    static struct timeval start;
    struct timeval start_time, curr_time, diff_time;

    //ethernet = (struct sniff_ethernet*)(pkt_data);
    //ip = (struct sniff_ip*)(pkt_data + SIZE_ETHERNET);
    ip = (struct sniff_ip*)(pkt_data + g_linkhdrlen);
    size_ip = IP_HL(ip)*4;
    if (size_ip < 20) {
        printf("   * Invalid IP header length: %u bytes\n", size_ip);
        return;
    }
    //tcp = (struct sniff_tcp*)(pkt_data + SIZE_ETHERNET + size_ip);
    tcp = (struct sniff_tcp*)(pkt_data + g_linkhdrlen + size_ip);
    size_tcp = TH_OFF(tcp)*4;
    if (size_tcp < 20) {
        printf("   * Invalid TCP header length: %u bytes\n", size_tcp);
        return;
    }
    //payload = (u_char *)(pkt_data + SIZE_ETHERNET + size_ip + size_tcp);
    payload = (u_char *)(pkt_data + g_linkhdrlen + size_ip + size_tcp);
    strncpy(src_ip, inet_ntoa(ip->ip_src), 16);
    strncpy(dst_ip, inet_ntoa(ip->ip_dst), 16);

    /* Find the timestamp of the first packet */
    if(start_found == false) {
        start_found = true;
        start = pkt_header->ts;
    }

    /* Find the relative timestamp difference between the first packet and the current packet */
    start_time.tv_sec = start.tv_sec;
    start_time.tv_usec = start.tv_usec;
    curr_time = pkt_header->ts;
    timeval_subtract(&diff_time, &curr_time, &start_time);
    
    // relative_timestamp ip_id ip_src ip_dst src_port dst_port seq_num ack_num win_size total_size data_size
    printf("%u\t%u\t%s\t%s\t%u\t%u\t%u\t%u\t%u\t%u\t%u\n",
            diff_time.tv_sec*1000000 + diff_time.tv_usec,
            ntohs(ip->ip_id),
            src_ip,
            dst_ip,
            ntohs(tcp->th_sport),
            ntohs(tcp->th_dport),
            ntohl(tcp->th_seq),
            ntohl(tcp->th_ack),
            ntohs(tcp->th_win),
            pkt_header->len,
            pkt_header->len - g_linkhdrlen - size_ip - size_tcp);
}

/* http://www.gnu.org/software/libc/manual/html_node/Elapsed-Time.html */
/* Get the difference in timestamps to the microsecond */
int timeval_subtract (struct timeval *result, struct timeval *x, struct timeval *y) {
    /* Perform the carry for the later subtraction by updating y. */
    if (x->tv_usec < y->tv_usec) {
        int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
        y->tv_usec -= 1000000 * nsec;
        y->tv_sec += nsec;
    }
    if (x->tv_usec - y->tv_usec > 1000000) {
        int nsec = (x->tv_usec - y->tv_usec) / 1000000;
        y->tv_usec += 1000000 * nsec;
        y->tv_sec -= nsec;
    }

    /* Compute the time remaining to wait.
     *           tv_usec is certainly positive. */
    result->tv_sec = x->tv_sec - y->tv_sec;
    result->tv_usec = x->tv_usec - y->tv_usec;

    /* Return 1 if result is negative. */
    return x->tv_sec < y->tv_sec;
}
